Skip to content

Commit 64a8ecb

Browse files
authored
Improved autodetection of TeensyToAny devices (#45)
* Improved autodetection of TeensyToAny devices * lint * Provide a teensytoany_list command which lists avialable devices * Deprecate things and add context manager
1 parent 2188132 commit 64a8ecb

6 files changed

Lines changed: 109 additions & 67 deletions

File tree

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# History
22

3+
## 0.11.0 (2025-06-14)
4+
5+
* Try to automatically connect to devices with Manufacturer TeensyToAny. This
6+
requires firmware version 0.13.0 and up.
7+
38
## 0.10.2 (2025-06-02)
49

510
* Fix I2C scanning command line utility by absorping the errors when no device

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def get_version_and_cmdclass(pkg_path):
5353
'console_scripts': [
5454
'teensytoany_programmer=teensytoany.programmer:teensytoany_programmer',
5555
'teensytoany_i2c_scan=teensytoany.i2c_scan:main',
56+
'teensytoany_list=teensytoany.list:main',
5657
],
5758
},
5859
packages=find_packages(include=['teensytoany']),

teensytoany/i2c_scan.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import click
2+
from packaging.version import Version
23

34
import teensytoany
45
from teensytoany import TeensyToAny
@@ -97,7 +98,6 @@ def i2c_scan(
9798
seven_bit_mode=False,
9899
verbose=False,
99100
):
100-
from packaging.version import Version
101101

102102
teensy = TeensyToAny(serial_number=serial_number)
103103
if Version(teensy.version) < Version('0.12.0'):

teensytoany/list.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import sys
2+
3+
import click
4+
5+
import teensytoany
6+
from teensytoany import TeensyToAny
7+
8+
9+
@click.command(epilog=f"Version {teensytoany.__version__}")
10+
@click.option(
11+
'--manufacturer',
12+
type=str,
13+
default="TeensyToAny",
14+
show_default=True,
15+
help="Manufacturer of the device to list.",
16+
)
17+
@click.option(
18+
'--teensyduino',
19+
is_flag=True,
20+
default=False,
21+
help=(
22+
"List devices with manufacturer 'TeensyToAny'. "
23+
"This is a shortcut for --manufacturer=TeensyToAny"
24+
),
25+
)
26+
@click.version_option(teensytoany.__version__)
27+
def main(
28+
manufacturer="TeensyToAny",
29+
teensyduino=False,
30+
):
31+
"""
32+
List available TeensyToAny devices.
33+
"""
34+
if teensyduino:
35+
manufacturer = "Teensyduino"
36+
37+
try:
38+
device_serial_numbers = TeensyToAny.device_serial_number_pairs(manufacturer=manufacturer)
39+
except RuntimeError:
40+
click.echo(f"Error: Could not find any devices with manufacturer '{manufacturer}'.")
41+
sys.exit(1)
42+
43+
for device, serial_number in device_serial_numbers:
44+
click.echo(f"Port: {device} -- Serial Number: {serial_number}")

teensytoany/programmer.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,17 @@ def teensytoany_programmer(
6666
)
6767
return
6868

69-
print('Programming Teensy with:')
69+
print('Programming please wait...')
7070
teensytoany.TeensyToAny.program_firmware(
7171
serial_number,
7272
mcu=mcu,
7373
version=firmware_version,
7474
variant=firmware_variant
7575
)
76-
teensy = teensytoany.TeensyToAny(serial_number)
77-
print(f"TeensyToAny version: {teensy.version}")
78-
print(f"TeensyToAny variant: {firmware_variant}")
79-
print(f"TeensyToAny serial_number: {teensy.serial_number}")
80-
teensy.close()
76+
with teensytoany.TeensyToAny(serial_number) as teensy:
77+
print(f"TeensyToAny version: {teensy.version}")
78+
print(f"TeensyToAny variant: {firmware_variant}")
79+
print(f"TeensyToAny serial_number: {teensy.serial_number}")
8180

8281

8382
if __name__ == '__main__':

teensytoany/teensytoany.py

Lines changed: 53 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,60 +3,13 @@
33
from contextlib import contextmanager
44
from time import sleep
55
from typing import Sequence
6+
from warnings import warn
67

78
from packaging.version import Version
89
from serial import LF, Serial
910
from serial.tools.list_ports import comports
1011

11-
__all__ = ['TeensyToAny', 'known_devices', 'known_serial_numbers']
12-
13-
known_devices = [
14-
# Example device structure
15-
# These include useful information about the hardware that is created and
16-
# burned in with the serial numbers.
17-
# Since the same VID/PID can be assigned to multiple devices,
18-
# We must log the individual serial numbers to know what they all do.
19-
{
20-
'serial_number': '4725230',
21-
'device_name': 'teensytoany',
22-
'mcu': 'TEENSY32',
23-
},
24-
{
25-
'serial_number': '5032260',
26-
'device_name': 'teensytoany',
27-
'mcu': 'TEENSY32',
28-
},
29-
{
30-
'serial_number': '4725070',
31-
'device_name': 'teensytoany',
32-
'mcu': 'TEENSY32',
33-
},
34-
{
35-
'serial_number': '4726520',
36-
'device_name': 'teensytoany',
37-
'mcu': 'TEENSY32',
38-
},
39-
{
40-
'serial_number': '5032540',
41-
'device_name': 'teensytoany',
42-
'mcu': 'TEENSY32',
43-
},
44-
{
45-
'serial_number': '4728790',
46-
'device_name': 'teensytoany',
47-
'mcu': 'TEENSY32',
48-
},
49-
{
50-
'serial_number': '5955040',
51-
'device_name': 'teensytoany',
52-
'mcu': 'TEENSY32',
53-
},
54-
]
55-
56-
known_serial_numbers = [
57-
d['serial_number']
58-
for d in known_devices
59-
]
12+
__all__ = ['TeensyToAny']
6013

6114

6215
class TeensyToAny:
@@ -76,8 +29,6 @@ class TeensyToAny:
7629
]
7730

7831
_device_name = "TeensyToAny"
79-
_known_device = known_devices
80-
_known_serial_numbers = known_serial_numbers
8132

8233
@staticmethod
8334
def find(serial_numbers=None):
@@ -101,13 +52,18 @@ def find(serial_numbers=None):
10152
computer but that may not be associated with the TeensyToAny boards.
10253
10354
"""
104-
pairs = TeensyToAny._device_serial_number_pairs(
55+
pairs = TeensyToAny.device_serial_number_pairs(
10556
serial_numbers=serial_numbers)
10657
devices, _ = zip(*pairs)
10758
return devices
10859

10960
@staticmethod
110-
def list_all_serial_numbers(serial_numbers=None, *, device_name=None):
61+
def list_all_serial_numbers(
62+
serial_numbers=None,
63+
*,
64+
device_name=None,
65+
manufacturer="TeensyToAny",
66+
):
11167
"""Find all the currently connected TeensyToAny serial numbers.
11268
11369
Parameters
@@ -128,8 +84,11 @@ def list_all_serial_numbers(serial_numbers=None, *, device_name=None):
12884
computer but that may not be associated with the TeensyToAny boards.
12985
13086
"""
131-
pairs = TeensyToAny._device_serial_number_pairs(
132-
serial_numbers=serial_numbers, device_name=device_name)
87+
pairs = TeensyToAny.device_serial_number_pairs(
88+
serial_numbers=serial_numbers,
89+
device_name=device_name,
90+
manufacturer=manufacturer,
91+
)
13392
_, serial_numbers = zip(*pairs)
13493
return serial_numbers
13594

@@ -177,16 +136,39 @@ def _get_latest_available_firmware_online(*, timeout=2):
177136
return latest_release_version
178137

179138
@staticmethod
180-
def _device_serial_number_pairs(serial_numbers=None, *, device_name=None):
139+
def _device_serial_number_pairs(
140+
serial_numbers=None,
141+
*,
142+
device_name=None,
143+
manufacturer="TeensyToAny",
144+
):
145+
warn(
146+
"The TeensyToAny._device_serial_number_pairs function is deprecated. "
147+
"Use TeensyToAny.device_serial_number_pairs instead.",
148+
stacklevel=2,
149+
)
150+
return TeensyToAny.device_serial_number_pairs(
151+
serial_numbers=serial_numbers,
152+
device_name=device_name,
153+
manufacturer=manufacturer,
154+
)
155+
156+
@staticmethod
157+
def device_serial_number_pairs(
158+
serial_numbers=None,
159+
*,
160+
device_name=None,
161+
manufacturer="TeensyToAny",
162+
):
181163
if device_name is None:
182164
device_name = TeensyToAny._device_name
183165
com = comports()
184166
pairs = [
185167
(c.device, c.serial_number)
186168
for c in com
187169
if ((c.vid, c.pid) in TeensyToAny.VID_PID_s and
188-
(serial_numbers is None or
189-
c.serial_number in serial_numbers))
170+
((serial_numbers is None and c.manufacturer == manufacturer) or
171+
(serial_numbers and c.serial_number in serial_numbers)))
190172
]
191173
if len(pairs) == 0:
192174
raise RuntimeError(
@@ -397,13 +379,24 @@ def open(self):
397379
self.close()
398380
raise e
399381

382+
def __enter__(self):
383+
"""Context manager for opening the device."""
384+
return self
385+
386+
def __exit__(self, exc_type, exc_value, traceback):
387+
"""Context manager for closing the device."""
388+
self.close()
389+
if exc_type is not None:
390+
raise exc_value.with_traceback(traceback)
391+
return False
392+
400393
def _open(self):
401394
if self._requested_serial_number is None:
402-
serial_numbers = self._known_serial_numbers
395+
serial_numbers = None
403396
else:
404397
serial_numbers = [self._requested_serial_number]
405398

406-
port, found_serial_number = self._device_serial_number_pairs(
399+
port, found_serial_number = self.device_serial_number_pairs(
407400
serial_numbers=serial_numbers, device_name=self._device_name)[0]
408401

409402
self._serial = Serial(

0 commit comments

Comments
 (0)