Repository: ramonaoptics/python-teensytoany
Pull request #38: Board specific programming
Author: login: vetonsaliu8
Created: 2025-05-16T14:17:20Z | Merged: 2025-05-16T18:02:11Z

Add the ability to specify a firmware variant for programming.  
* [ ] Add note in `HISTORY.md` of the user
* [ ] Add tests if relevant.
* [ ] Add your name to the `AUTHORS.md` file




Diff:
diff --git a/.pylintrc b/.pylintrc
index c5e0a5f..ca0061a 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -10,3 +10,4 @@ disable=
     C0116,  # missing-function-docstring
     R0904,  # too-many-public-methods
     R0914,  # too-many-locals
+    R0913,  # too-many-arguments -- perhaps we can increase this limit from 5???
diff --git a/teensytoany/programmer.py b/teensytoany/programmer.py
index 4e985ba..0880115 100644
--- a/teensytoany/programmer.py
+++ b/teensytoany/programmer.py
@@ -26,6 +26,12 @@
     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,
@@ -38,25 +44,41 @@ 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 firmware version {firmware_version} for {mcu}.")
-            teensytoany.TeensyToAny.download_firmware(mcu=mcu, version=firmware_version)
+            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(f'Programming irmware version {mcu} {firmware_version}', end='')
-    teensytoany.TeensyToAny.program_firmware(serial_number, mcu=mcu, version=firmware_version)
+    print('Programming Teensy with:')
+    teensytoany.TeensyToAny.program_firmware(
+        serial_number,
+        mcu=mcu,
+        version=firmware_version,
+        variant=firmware_variant
+    )
     teensy = teensytoany.TeensyToAny(serial_number)
     print(f"TeensyToAny version: {teensy.version}")
+    print(f"TeensyToAny variant: {firmware_variant}")
     print(f"TeensyToAny serial_number: {teensy.serial_number}")
     teensy.close()
 
-if  __name__ == '__main__':
+
+if __name__ == '__main__':
     teensytoany_programmer()
diff --git a/teensytoany/teensytoany.py b/teensytoany/teensytoany.py
index a055995..35c3027 100644
--- a/teensytoany/teensytoany.py
+++ b/teensytoany/teensytoany.py
@@ -145,7 +145,8 @@ def get_latest_available_firmware_version(
         try:
             if online:
                 latest = TeensyToAny._get_latest_available_firmware_online(
-                    timeout=timeout)
+                    timeout=timeout
+                )
         except Exception:  # pylint: disable=broad-except
             pass
 
@@ -197,7 +198,7 @@ def _device_serial_number_pairs(serial_numbers=None, *, device_name=None):
     def mcu(self):
         return self._ask('mcu')
 
-    def _update_firmware(self, *, mcu=None, force=False, timeout=2):
+    def _update_firmware(self, *, mcu=None, variant: str=None, force=False, timeout=2):
         current_version = self.version
         serial_number = self.serial_number
         if mcu is None:
@@ -220,6 +221,7 @@ def _update_firmware(self, *, mcu=None, force=False, timeout=2):
                 serial_number,
                 mcu=mcu,
                 version=latest_version,
+                variant=variant,
                 timeout=timeout
             )
             # Reraise any exceptions that were caught
@@ -242,9 +244,12 @@ def _find_local_versions(*, mcu=None):
         return versions
 
     @staticmethod
-    def _generate_firmware_filename(*, mcu, version):
+    def _generate_firmware_filename(*, mcu, version, variant: str=None):
         firmware_dir = TeensyToAny._generate_firmware_directory(mcu=mcu)
-        firmware_filename = firmware_dir / f"{version}" / "firmware.hex"
+        if variant is None:
+            firmware_filename = firmware_dir / f"{version}" / "firmware.hex"
+        else:
+            firmware_filename = firmware_dir / f"{version}" / f"firmware_{variant}.hex"
         return firmware_filename
 
     @staticmethod
@@ -260,11 +265,19 @@ def _generate_firmware_directory(*, mcu):
         return firmware_dir
 
     @staticmethod
-    def download_firmware(*, mcu, version, timeout=2):
-        firmware_filename = TeensyToAny._generate_firmware_filename(mcu=mcu, version=version)
+    def download_firmware(*, mcu, version, variant: str=None, timeout=2):
+        firmware_filename = TeensyToAny._generate_firmware_filename(
+            mcu=mcu,
+            version=version,
+            variant=variant
+        )
 
         import requests  # pylint: disable=import-outside-toplevel
-        file_url = f"https://github.com/ramonaoptics/teensy-to-any/releases/download/{version}/firmware_{mcu.lower()}.hex"  # noqa # pylint: disable=line-too-long
+        release_url = f"https://github.com/ramonaoptics/teensy-to-any/releases/download/{version}/"
+        if variant is None:
+            file_url = release_url + f"firmware_{mcu.lower()}.hex"
+        else:
+            file_url = release_url + f"firmware_{mcu.lower()}_{variant}.hex"
         response = requests.get(file_url, timeout=timeout)
         if response.status_code != 200:
             raise RuntimeError("Failed to download firmware")
@@ -279,7 +292,16 @@ def download_firmware(*, mcu, version, timeout=2):
         return firmware_filename
 
     @staticmethod
-    def program_firmware(serial_number=None, *, mcu=None, version=None, timeout=2):
+    def program_firmware(
+        serial_number=None,
+        *,
+        mcu=None,
+        version=None,
+        variant: str=None,
+        verbose=False,
+        wait=False,
+        timeout=2,
+    ):
         if serial_number is None:
             available_serial_numbers = TeensyToAny.list_all_serial_numbers()
             if len(available_serial_numbers) == 0:
@@ -302,14 +324,33 @@ def program_firmware(serial_number=None, *, mcu=None, version=None, timeout=2):
             # there is no serial number specificity
             raise RuntimeError("We do not supporting programing TeensyToAny devices on Windows")
 
-        firmware_filename = TeensyToAny._generate_firmware_filename(mcu=mcu, version=version)
+        firmware_filename = TeensyToAny._generate_firmware_filename(
+            mcu=mcu,
+            version=version,
+            variant=variant
+        )
 
         if not firmware_filename.is_file():
-            TeensyToAny.download_firmware(mcu=mcu, version=version, timeout=timeout)
+            TeensyToAny.download_firmware(
+                mcu=mcu,
+                version=version,
+                variant=variant,
+                timeout=timeout
+            )
 
+        if verbose:
+            verbose = ['-v',]
+        else:
+            verbose = []
+
+        if wait:
+            wait = ['-w',]
+        else:
+            wait = []
         cmd_list = [
             'teensy_loader_cli',
             '-s',
+        ] + verbose + wait + [
             f'--mcu={mcu}',
             f'--serial-number={serial_number}',
             str(firmware_filename),
@@ -573,9 +614,9 @@ def i2c_1_write_uint16(self, address: int, register_address: int, data: int):
         self._ask(cmd)
 
     def i2c_1_write_read(self,
-                       address: int,
-                       data: Sequence,
-                       num_bytes: int) -> Sequence:
+                         address: int,
+                         data: Sequence,
+                         num_bytes: int) -> Sequence:
         if len(data) != 2:
             raise ValueError("data must be of length 2")
         if num_bytes not in [1, 2]:


Reviews:
user: login: hmaarrfk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T17:37:23Z
user: login: hmaarrfk
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T17:51:30Z

Review comments:
diff_hunk: @@ -260,11 +265,18 @@ def _generate_firmware_directory(*, mcu):
         return firmware_dir
 
     @staticmethod
-    def download_firmware(*, mcu, version, timeout=2):
-        firmware_filename = TeensyToAny._generate_firmware_filename(mcu=mcu, version=version)
+    def download_firmware(*, mcu, version, variant: str=None, timeout=2):
+        firmware_filename = TeensyToAny._generate_firmware_filename(
+            mcu=mcu,
+            version=version,
+            variant=variant
+        )
 
         import requests  # pylint: disable=import-outside-toplevel
-        file_url = f"https://github.com/ramonaoptics/teensy-to-any/releases/download/{version}/firmware_{mcu.lower()}.hex"  # noqa # pylint: disable=line-too-long
+        if variant is None:
+            file_url = f"https://github.com/ramonaoptics/teensy-to-any/releases/download/{version}/firmware_{mcu.lower()}.hex"  # noqa # pylint: disable=line-too-long
+        else:
+            file_url = f"https://github.com/ramonaoptics/teensy-to-any/releases/download/{version}/firmware_{mcu.lower()}_{variant}.hex"  # noqa # pylint: disable=line-too-long
path: teensytoany/teensytoany.py
user: login: hmaarrfk
body: ```suggestion
        release_url = f"https://github.com/ramonaoptics/teensy-to-any/releases/download/{version}/"
        if variant is None:
            file_url = release_url + f"firmware_{mcu.lower()}.hex"
        else:
            file_url = release_url + f"firmware_{mcu.lower()}_{variant}.hex"
```

sometimes line too long is good warning!
created_at: 2025-05-16T17:37:22Z
author_association: MEMBER
diff_hunk: @@ -38,25 +44,38 @@ 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 firmware version {firmware_version} for {mcu}.")
-            teensytoany.TeensyToAny.download_firmware(mcu=mcu, version=firmware_version)
+            print(f"Downloading {variant_str}firmware version {firmware_version} for {mcu_to_download}.") # noqa
path: teensytoany/programmer.py
user: login: hmaarrfk
body: ```suggestion
            print(
                f"Downloading {variant_str} firmware version {firmware_version} "
                f"for {mcu_to_download}."
            )
```
created_at: 2025-05-16T17:51:29Z
author_association: MEMBER

PR comments:
user: login: hmaarrfk
created_at: 2025-05-16T17:52:07Z
body: Can you bump to 0.9.0 and add some release notes as well:
https://github.com/ramonaoptics/python-teensytoany/blob/main/HISTORY.md
author_association: MEMBER
user: login: hmaarrfk
created_at: 2025-05-16T18:02:06Z
body: Hmm. lets just use github release notes.
author_association: MEMBER