diff --git a/HISTORY.md b/HISTORY.md index 794db72..76d4749 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # History +## 0.13.0 (2025-08-22) + +* Consolidate multiple entry points into a single `teensytoany` command with subcommands: + - `teensytoany programmer` (replaces `teensytoany_programmer`) + - `teensytoany i2c-scan` (replaces `teensytoany_i2c_scan`) + - `teensytoany list` (replaces `teensytoany_list`) +* Maintain backward compatibility with deprecation warnings for old entry points + ## 0.12.1 (2025-08-22) * Add `fastled_set_max_refresh_rate` method to control FastLED refresh rate. diff --git a/setup.py b/setup.py index 851864a..bada6ad 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,8 @@ def get_version_and_cmdclass(pkg_path): name='teensytoany', entry_points={ 'console_scripts': [ - 'teensytoany_programmer=teensytoany.programmer:teensytoany_programmer', + 'teensytoany=teensytoany.cli:teensytoany_cli', + 'teensytoany_programmer=teensytoany.programmer:main', 'teensytoany_i2c_scan=teensytoany.i2c_scan:main', 'teensytoany_list=teensytoany.list:main', ], diff --git a/teensytoany/cli.py b/teensytoany/cli.py new file mode 100644 index 0000000..fdc3e63 --- /dev/null +++ b/teensytoany/cli.py @@ -0,0 +1,153 @@ +import click + +import teensytoany +from teensytoany.i2c_scan import i2c_scan +from teensytoany.list import teensytoany_list +from teensytoany.programmer import teensytoany_programmer + + +@click.group(epilog=f"Version {teensytoany.__version__}") +@click.version_option(teensytoany.__version__) +def teensytoany_cli(): + """TeensyToAny command line interface""" + + +@teensytoany_cli.command() +@click.option( + '--serial-number', + default=None, + help=( + 'Serial number of the Teensy device to program. ' + 'If not provided and only one Teensy device found, ' + 'it will be programmed.' + ) +) +@click.option( + '--mcu', + type=click.Choice(['TEENSY40', 'TEENSY32']), + default='TEENSY40', + help='Microcontroller to program.' +) +@click.option( + '--firmware-version', + default=None, + type=str, + help='Firmware version to program. If not provided, the latest version will be programmed.' +) +@click.option( + '--firmware-variant', + default=None, + type=str, + help='Firmware variant to program. If not provided, the standard variant will be programmed.' +) +@click.option( + '--download-only', + is_flag=True, + default=False, + help='Download the firmware only, do not program the device.' +) +def programmer( + serial_number=None, + mcu='TEENSY40', + firmware_version=None, + firmware_variant=None, + download_only=False +): + """Program a Teensy device with a given firmware version""" + # pylint: disable=duplicate-code + teensytoany_programmer( + serial_number=serial_number, + mcu=mcu, + firmware_version=firmware_version, + firmware_variant=firmware_variant, + download_only=download_only + ) + + +@teensytoany_cli.command() +@click.option( + '--serial-number', '-s', + type=str, + default=None, + show_default=False, + help='Serial number of the Teensy device.' +) +@click.option( + '--interface', + '-i', + type=int, + default=0, + show_default=True, + help='I2C interface to use (0 for I2C, 1 for I2C1).' +) +@click.option( + '--seven-bit-mode', + '-7', + is_flag=True, + default=False, + help=( + 'Report addresses in 7-bit mode instead of 8-bit mode. ' + 'By default, addresses are reported in 8-bit mode.' + ), +) +@click.option( + '--baud-rate', + type=int, + default=100_100, + show_default=True, + help='Baud rate for the I2C bus.', +) +@click.option( + '--verbose', + is_flag=True, + default=False, + help='Report in verbose mode, print out all pinged addresses.', +) +def i2c_scan_command( + serial_number=None, + interface=0, + seven_bit_mode=False, + baud_rate=100_100, + verbose=False, +): + """Scan I2C devices connected to TeensyToAny""" + # pylint: disable=duplicate-code + i2c_scan( + serial_number=serial_number, + interface=interface, + baud_rate=baud_rate, + seven_bit_mode=seven_bit_mode, + verbose=verbose, + ) + + +@teensytoany_cli.command() +@click.option( + '--manufacturer', + type=str, + default="TeensyToAny", + show_default=True, + help="Manufacturer of the device to list.", +) +@click.option( + '--teensyduino', + is_flag=True, + default=False, + help=( + "List devices with manufacturer 'TeensyToAny'. " + "This is a shortcut for --manufacturer=TeensyToAny" + ), +) +def list_command( + manufacturer="TeensyToAny", + teensyduino=False, +): + """List available TeensyToAny devices""" + teensytoany_list( + manufacturer=manufacturer, + teensyduino=teensyduino, + ) + + +if __name__ == '__main__': + teensytoany_cli() diff --git a/teensytoany/i2c_scan.py b/teensytoany/i2c_scan.py index 6005e2f..2ecd985 100644 --- a/teensytoany/i2c_scan.py +++ b/teensytoany/i2c_scan.py @@ -52,6 +52,11 @@ def main( baud_rate=100_100, verbose=False, ): + click.echo( + "WARNING: teensytoany_i2c_scan is deprecated. Use 'teensytoany i2c_scan' instead.", + err=True + ) + # pylint: disable=duplicate-code return i2c_scan( serial_number=serial_number, interface=interface, diff --git a/teensytoany/list.py b/teensytoany/list.py index a31347c..f91a96e 100644 --- a/teensytoany/list.py +++ b/teensytoany/list.py @@ -6,6 +6,26 @@ from teensytoany import TeensyToAny +def teensytoany_list( + manufacturer="TeensyToAny", + teensyduino=False, +): + """ + List available TeensyToAny devices. + """ + if teensyduino: + manufacturer = "Teensyduino" + + try: + device_serial_numbers = TeensyToAny.device_serial_number_pairs(manufacturer=manufacturer) + except RuntimeError: + click.echo(f"Error: Could not find any devices with manufacturer '{manufacturer}'.") + sys.exit(1) + + for device, serial_number in device_serial_numbers: + click.echo(f"Port: {device} -- Serial Number: {serial_number}") + + @click.command(epilog=f"Version {teensytoany.__version__}") @click.option( '--manufacturer', @@ -31,14 +51,8 @@ def main( """ List available TeensyToAny devices. """ - if teensyduino: - manufacturer = "Teensyduino" - - try: - device_serial_numbers = TeensyToAny.device_serial_number_pairs(manufacturer=manufacturer) - except RuntimeError: - click.echo(f"Error: Could not find any devices with manufacturer '{manufacturer}'.") - sys.exit(1) - - for device, serial_number in device_serial_numbers: - click.echo(f"Port: {device} -- Serial Number: {serial_number}") + click.echo("WARNING: teensytoany_list is deprecated. Use 'teensytoany list' instead.", err=True) + teensytoany_list( + manufacturer=manufacturer, + teensyduino=teensyduino, + ) diff --git a/teensytoany/programmer.py b/teensytoany/programmer.py index 678cf04..596da1a 100644 --- a/teensytoany/programmer.py +++ b/teensytoany/programmer.py @@ -3,6 +3,45 @@ import teensytoany +def teensytoany_programmer( + serial_number=None, + mcu='TEENSY40', + firmware_version=None, + firmware_variant=None, + download_only=False +): + """Program a Teensy device with a given firmware version""" + variant_str = f"variant {firmware_variant} of " if firmware_variant else "" + if download_only: + for mcu_to_download in ['TEENSY40', 'TEENSY32']: + if firmware_version is None: + firmware_version = teensytoany.TeensyToAny.get_latest_available_firmware_version( + mcu=mcu_to_download, online=True, local=False + ) + print( + f"Downloading {variant_str} firmware version {firmware_version} " + f"for {mcu_to_download}." + ) + teensytoany.TeensyToAny.download_firmware( + mcu=mcu_to_download, + version=firmware_version, + variant=firmware_variant + ) + return + + print('Programming please wait...') + teensytoany.TeensyToAny.program_firmware( + serial_number, + mcu=mcu, + version=firmware_version, + variant=firmware_variant + ) + with teensytoany.TeensyToAny(serial_number) as teensy: + print(f"TeensyToAny version: {teensy.version}") + print(f"TeensyToAny variant: {firmware_variant}") + print(f"TeensyToAny serial_number: {teensy.serial_number}") + + @click.command(epilog=f"Version {teensytoany.__version__}") @click.option( '--serial-number', @@ -40,7 +79,7 @@ ) # Make the epligue print the version @click.version_option(teensytoany.__version__) -def teensytoany_programmer( +def main( serial_number=None, mcu='TEENSY40', firmware_version=None, @@ -48,36 +87,19 @@ def teensytoany_programmer( download_only=False ): """Program a Teensy device with a given firmware version""" - variant_str = f"variant {firmware_variant} of " if firmware_variant else "" - if download_only: - for mcu_to_download in ['TEENSY40', 'TEENSY32']: - if firmware_version is None: - firmware_version = teensytoany.TeensyToAny.get_latest_available_firmware_version( - mcu=mcu_to_download, online=True, local=False - ) - print( - f"Downloading {variant_str} firmware version {firmware_version} " - f"for {mcu_to_download}." - ) - teensytoany.TeensyToAny.download_firmware( - mcu=mcu_to_download, - version=firmware_version, - variant=firmware_variant - ) - return - - print('Programming please wait...') - teensytoany.TeensyToAny.program_firmware( - serial_number, + click.echo( + "WARNING: teensytoany_programmer is deprecated. Use 'teensytoany programmer' instead.", + err=True + ) + # pylint: disable=duplicate-code + teensytoany_programmer( + serial_number=serial_number, mcu=mcu, - version=firmware_version, - variant=firmware_variant + firmware_version=firmware_version, + firmware_variant=firmware_variant, + download_only=download_only ) - with teensytoany.TeensyToAny(serial_number) as teensy: - print(f"TeensyToAny version: {teensy.version}") - print(f"TeensyToAny variant: {firmware_variant}") - print(f"TeensyToAny serial_number: {teensy.serial_number}") if __name__ == '__main__': - teensytoany_programmer() + main()