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 23from typing import List 24 25import pw_arduino_build.log 26 27_LOG = logging.getLogger('teensy_detector') 28 29 30class UnknownArduinoCore(Exception): 31 """Exception raised when a given core can not be found.""" 32 33 34def log_subprocess_output(level, output): 35 """Logs subprocess output line-by-line.""" 36 37 lines = output.decode('utf-8', errors='replace').splitlines() 38 for line in lines: 39 _LOG.log(level, line) 40 41 42class BoardInfo(typing.NamedTuple): 43 """Information about a connected dev board.""" 44 45 dev_name: str 46 usb_device_path: str 47 protocol: str 48 label: str 49 arduino_upload_tool_name: str 50 51 def test_runner_args(self) -> List[str]: 52 return [ 53 "--set-variable", 54 f"serial.port.protocol={self.protocol}", 55 "--set-variable", 56 f"serial.port={self.usb_device_path}", 57 "--set-variable", 58 f"serial.port.label={self.dev_name}", 59 ] 60 61 62def detect_boards(arduino_package_path=False) -> list: 63 """Detect attached boards, returning a list of Board objects.""" 64 65 teensy_core = Path() 66 if arduino_package_path: 67 teensy_core = Path(arduino_package_path) 68 else: 69 teensy_core = Path("third_party/arduino/cores/teensy") 70 if not teensy_core.exists(): 71 teensy_core = Path( 72 "third_party/pigweed/third_party/arduino/cores/teensy" 73 ) 74 75 if not teensy_core.exists(): 76 raise UnknownArduinoCore 77 78 teensy_device_line_regex = re.compile( 79 r"^(?P<address>[^ ]+) (?P<dev_name>[^ ]+) " 80 r"\((?P<label>[^)]+)\) ?(?P<rest>.*)$" 81 ) 82 83 boards = [] 84 detect_command = [ 85 (teensy_core / "hardware" / "tools" / "teensy_ports") 86 .absolute() 87 .as_posix(), 88 "-L", 89 ] 90 91 # TODO(tonymd): teensy_ports -L on windows does not return the right port 92 # string Example: 93 # 94 # $ teensy_ports -L 95 # Port_#0001.Hub_#0003 COM3 (Teensy 3.6) Serial 96 # 97 # So we get "-port=Port_#0001.Hub_#0003" 98 # But it should be "-port=usb:0/140000/0/1" 99 100 process = subprocess.run( 101 detect_command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT 102 ) 103 if process.returncode != 0: 104 _LOG.error("Command failed with exit code %d.", process.returncode) 105 _LOG.error("Full command:") 106 _LOG.error("") 107 _LOG.error(" %s", " ".join(detect_command)) 108 _LOG.error("") 109 _LOG.error("Process output:") 110 log_subprocess_output(logging.ERROR, process.stdout) 111 _LOG.error('') 112 for line in process.stdout.decode("utf-8", errors="replace").splitlines(): 113 device_match_result = teensy_device_line_regex.match(line) 114 if device_match_result: 115 teensy_device = device_match_result.groupdict() 116 boards.append( 117 BoardInfo( 118 dev_name=teensy_device["dev_name"], 119 usb_device_path=teensy_device["address"], 120 protocol="Teensy", 121 label=teensy_device["label"], 122 arduino_upload_tool_name="teensyloader", 123 ) 124 ) 125 return boards 126 127 128def main(): 129 """This detects and then displays all attached discovery boards.""" 130 131 pw_arduino_build.log.install(logging.INFO) 132 133 boards = detect_boards() 134 if not boards: 135 _LOG.info("No attached boards detected") 136 for idx, board in enumerate(boards): 137 _LOG.info("Board %d:", idx) 138 _LOG.info(" - Name: %s", board.label) 139 _LOG.info(" - Port: %s", board.dev_name) 140 _LOG.info(" - Address: %s", board.usb_device_path) 141 _LOG.info( 142 " - Test runner args: %s", " ".join(board.test_runner_args()) 143 ) 144 145 146if __name__ == "__main__": 147 main() 148