• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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