aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/tools/ci_check_duplicate_usb_vid_pid.py
diff options
context:
space:
mode:
Diffstat (limited to 'circuitpython/tools/ci_check_duplicate_usb_vid_pid.py')
-rw-r--r--circuitpython/tools/ci_check_duplicate_usb_vid_pid.py133
1 files changed, 133 insertions, 0 deletions
diff --git a/circuitpython/tools/ci_check_duplicate_usb_vid_pid.py b/circuitpython/tools/ci_check_duplicate_usb_vid_pid.py
new file mode 100644
index 0000000..d9e7d99
--- /dev/null
+++ b/circuitpython/tools/ci_check_duplicate_usb_vid_pid.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python3
+#
+# This file is part of the MicroPython project, http://micropython.org/
+#
+# The MIT License (MIT)
+#
+# Copyright (c) 2020 Michael Schroeder
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+from collections import defaultdict
+import argparse
+import pathlib
+import re
+import sys
+
+DEFAULT_CLUSTERLIST = {
+ "0x04D8:0xEC44": ["pycubed", "pycubed_mram", "pycubed_mram_v05", "pycubed_v05"],
+ "0x1B4F:0x8D24": ["sparkfun_qwiic_micro_no_flash", "sparkfun_qwiic_micro_with_flash"],
+ "0x1D50:0x6153": [
+ "jpconstantineau_pykey18",
+ "jpconstantineau_pykey44",
+ "jpconstantineau_pykey60",
+ "jpconstantineau_pykey87",
+ ],
+ "0x239A:0x8019": [
+ "circuitplayground_express",
+ "circuitplayground_express_crickit",
+ "circuitplayground_express_displayio",
+ ],
+ "0x239A:0x801F": ["trinket_m0_haxpress", "trinket_m0"],
+ "0x239A:0x8021": ["metro_m4_express", "cp32-m4"],
+ "0x239A:0x8023": ["feather_m0_express", "feather_m0_supersized"],
+ "0x239A:0x80A6": ["espressif_esp32s2_devkitc_1_n4r2", "espressif_saola_1_wrover"],
+ "0x239A:0x80AC": ["unexpectedmaker_feathers2", "unexpectedmaker_feathers2_prerelease"],
+ "0x239A:0x80C8": ["espressif_kaluga_1", "espressif_kaluga_1.3"],
+ "0x303A:0x7003": [
+ "espressif_esp32s3_devkitc_1_n8",
+ "espressif_esp32s3_devkitc_1_n8r2",
+ "espressif_esp32s3_devkitc_1_n8r8",
+ ],
+ "0x303A:0x7009": ["espressif_esp32s2_devkitc_1_n4", "espressif_esp32s2_devkitc_1_n4r2"],
+}
+
+cli_parser = argparse.ArgumentParser(description="USB VID/PID Duplicate Checker")
+
+
+def configboard_files():
+ """A pathlib glob search for all ports/*/boards/*/mpconfigboard.mk file
+ paths.
+
+ :returns: A ``pathlib.Path.glob()`` generator object
+ """
+ working_dir = pathlib.Path(__file__).resolve().parent.parent
+ return working_dir.glob("ports/**/boards/**/mpconfigboard.mk")
+
+
+def check_vid_pid(files, clusterlist):
+ """Compiles a list of USB VID & PID values for all boards, and checks
+ for duplicates. Exits with ``sys.exit()`` (non-zero exit code)
+ if duplicates are found, and lists the duplicates.
+ """
+
+ vid_pattern = re.compile(r"^USB_VID\s*=\s*(.*)", flags=re.M)
+ pid_pattern = re.compile(r"^USB_PID\s*=\s*(.*)", flags=re.M)
+ usb_pattern = re.compile(r"^CIRCUITPY_USB\s*=\s*0$|^IDF_TARGET = esp32c3$", flags=re.M)
+
+ usb_ids = defaultdict(set)
+ for board_config in files:
+ src_text = board_config.read_text()
+
+ usb_vid = vid_pattern.search(src_text)
+ usb_pid = pid_pattern.search(src_text)
+ non_usb = usb_pattern.search(src_text)
+ board_name = board_config.parts[-2]
+
+ if usb_vid and usb_pid:
+ id_group = f"0x{int(usb_vid.group(1), 16):04X}:0x{int(usb_pid.group(1), 16):04X}"
+ elif non_usb:
+ continue
+ else:
+ raise SystemExit(f"Could not parse {board_config}")
+
+ usb_ids[id_group].add(board_name)
+
+ duplicates = []
+ for key, boards in usb_ids.items():
+ if len(boards) == 1:
+ continue
+
+ # It is a cluster
+ cluster = set(clusterlist.get(key, []))
+ if cluster != boards:
+ if key == "":
+ duplicates.append(f"- Non-USB:\n" f" Boards: {', '.join(sorted(boards))}")
+ else:
+ duplicates.append(f"- VID/PID: {key}\n" f" Boards: {', '.join(sorted(boards))}")
+
+ if duplicates:
+ duplicates = "\n".join(duplicates)
+ duplicate_message = (
+ f"Duplicate VID/PID usage found!\n{duplicates}\n"
+ f"If you are open source maker, then you can request a PID from http://pid.codes\n"
+ f"Otherwise, companies should pay the USB-IF for a vendor ID: https://www.usb.org/getting-vendor-id"
+ )
+ sys.exit(duplicate_message)
+ else:
+ print("No USB PID duplicates found.")
+
+
+if __name__ == "__main__":
+ arguments = cli_parser.parse_args()
+
+ print("Running USB VID/PID Duplicate Checker...")
+
+ board_files = configboard_files()
+ check_vid_pid(board_files, DEFAULT_CLUSTERLIST)