|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +""" |
| 4 | +Updates the USB VIDs and PIDs of the supported flight controllers. |
| 5 | +
|
| 6 | +This file is part of Ardupilot methodic configurator. https://github.com/ArduPilot/MethodicConfigurator |
| 7 | +
|
| 8 | +SPDX-FileCopyrightText: 2024 Amilcar do Carmo Lucas <[email protected]> |
| 9 | +
|
| 10 | +SPDX-License-Identifier: GPL-3.0-or-later |
| 11 | +""" |
| 12 | + |
| 13 | +import io # Import io for StringIO |
| 14 | +import os |
| 15 | +import sys |
| 16 | +from typing import Union |
| 17 | + |
| 18 | +# Add ../ardupilot/libraries/AP_HAL_ChibiOS/hwdef/scripts to the PYTHON path |
| 19 | +scripts_path = os.path.abspath("../ardupilot/libraries/AP_HAL_ChibiOS/hwdef/scripts") |
| 20 | +init_file_path = os.path.join(scripts_path, "__init__.py") |
| 21 | + |
| 22 | +# Check if the __init__.py file exists |
| 23 | +if not os.path.exists(init_file_path): |
| 24 | + # Create the __init__.py file |
| 25 | + with open(init_file_path, "w") as init_file: |
| 26 | + init_file.write("# This __init__.py was created automatically\n") |
| 27 | + print(f"Created __init__.py at: {init_file_path}") |
| 28 | +else: |
| 29 | + print(f"__init__.py already exists at: {init_file_path}") |
| 30 | +sys.path.append(scripts_path) |
| 31 | + |
| 32 | +try: |
| 33 | + import chibios_hwdef |
| 34 | + |
| 35 | + print("Module imported successfully.") |
| 36 | +except ImportError as e: |
| 37 | + print(f"ImportError: {e}") |
| 38 | + |
| 39 | + |
| 40 | +# Define the base directory to crawl |
| 41 | +base_dir = "../ardupilot/libraries/AP_HAL_ChibiOS/hwdef/" |
| 42 | + |
| 43 | +# Define the search terms |
| 44 | +SEARCH_VENDOR_ID = "USB_VENDOR" |
| 45 | +SEARCH_VENDOR_STRING = "USB_STRING_MANUFACTURER" |
| 46 | +SEARCH_PRODUCT_ID = "USB_PRODUCT" |
| 47 | +SEARCH_PRODUCT_STRING = "USB_STRING_PRODUCT" |
| 48 | +APJ_BOARD_ID_SEARCH = "APJ_BOARD_ID" |
| 49 | + |
| 50 | +# Initialize the result dictionary |
| 51 | +result: dict[int, dict[str, Union[str, dict[int, str]]]] = {} |
| 52 | +apj_board_ids: dict[int, str] = {} |
| 53 | +apj_board_ids_bdshot: dict[int, str] = {} |
| 54 | +board_types: dict[str, int] = {} |
| 55 | + |
| 56 | + |
| 57 | +def read_board_types_txt(filepath: str = "../ardupilot/Tools/AP_Bootloader/board_types.txt") -> None: |
| 58 | + try: |
| 59 | + with open(filepath, encoding="utf-8") as file: |
| 60 | + for line in file: |
| 61 | + text = line.strip() |
| 62 | + parts = text.split() |
| 63 | + if len(parts) >= 2 and parts[0] not in {"#", ""}: |
| 64 | + board_types[parts[0]] = parts[1] |
| 65 | + except Exception as e: |
| 66 | + print(f"Error reading {filepath}: {e}") |
| 67 | + return |
| 68 | + |
| 69 | + |
| 70 | +def search_hwdef_files(base_directory: str) -> None: |
| 71 | + # Walk through the directory |
| 72 | + for dirpath, _dirnames, filenames in os.walk(base_directory): |
| 73 | + for filename in filenames: |
| 74 | + if filename == "hwdef.dat": |
| 75 | + hwdef_file_path = os.path.join(dirpath, filename) |
| 76 | + dirname = os.path.basename(dirpath) |
| 77 | + # process_hwdef_file(hwdef_file_path, dirname) |
| 78 | + c = chibios_hwdef.ChibiOSHWDef( |
| 79 | + outdir="/tmp", # noqa: S108 |
| 80 | + bootloader=False, |
| 81 | + signed_fw=False, |
| 82 | + hwdef=[hwdef_file_path], |
| 83 | + default_params_filepath=None, |
| 84 | + quiet=True, |
| 85 | + ) |
| 86 | + c.process_hwdefs() |
| 87 | + numeric_board_id = int(c.get_numeric_board_id()) |
| 88 | + # print(f"{dirname} {c.get_numeric_board_id()}") |
| 89 | + if "bdshot" in dirname: |
| 90 | + apj_board_ids_bdshot[numeric_board_id] = dirname |
| 91 | + else: |
| 92 | + apj_board_ids[numeric_board_id] = dirname |
| 93 | + vid, pid = c.get_USB_IDs() |
| 94 | + vid_name = c.get_config("USB_STRING_MANUFACTURER", default='"ArduPilot"') |
| 95 | + pid_name = c.get_config("USB_STRING_PRODUCT", default=dirname) |
| 96 | + print(f"{numeric_board_id} {dirname} {vid} {vid_name} {pid} {pid_name}") |
| 97 | + |
| 98 | + |
| 99 | +def process_hwdef_file(filepath: str, dirname: str) -> None: |
| 100 | + vendor_id = None |
| 101 | + vendor_string = None |
| 102 | + product_id = None |
| 103 | + product_string = None |
| 104 | + apj_board_id_str = None |
| 105 | + apj_board_id = None |
| 106 | + |
| 107 | + try: |
| 108 | + with open(filepath, encoding="utf-8") as file: |
| 109 | + for line in file: |
| 110 | + text = line.strip() |
| 111 | + parts = text.split() |
| 112 | + if len(parts) >= 2: |
| 113 | + if parts[0] == SEARCH_VENDOR_ID: |
| 114 | + # Extract vendor ID |
| 115 | + vendor_id = int(parts[1], 16) |
| 116 | + elif parts[0] == SEARCH_VENDOR_STRING: |
| 117 | + # Extract vendor name |
| 118 | + vendor_string = parts[1].strip('"').strip("'") |
| 119 | + elif parts[0] == SEARCH_PRODUCT_ID: |
| 120 | + # Extract product ID |
| 121 | + product_id = int(parts[1], 16) |
| 122 | + elif parts[0] == SEARCH_PRODUCT_STRING: |
| 123 | + # Extract product name |
| 124 | + product_string = parts[1].strip('"').strip("'") |
| 125 | + elif parts[0] == APJ_BOARD_ID_SEARCH: |
| 126 | + # Extract product name |
| 127 | + apj_board_id_str = parts[1] |
| 128 | + # elif parts[0] == "include": |
| 129 | + # return process_hwdef_file(os.path.join(os.path.dirname(filepath), parts[1]), dirname) |
| 130 | + |
| 131 | + except Exception as e: |
| 132 | + print(f"Error reading {filepath}: {e}") |
| 133 | + return |
| 134 | + |
| 135 | + if apj_board_id_str is None and product_id is None: |
| 136 | + print(f"Error {filepath} does not contain {APJ_BOARD_ID_SEARCH} nor {SEARCH_PRODUCT_ID}") |
| 137 | + return |
| 138 | + |
| 139 | + if apj_board_id_str: |
| 140 | + try: |
| 141 | + apj_board_id = int(apj_board_id_str) |
| 142 | + except ValueError: |
| 143 | + # use the Tools\AP_Bootloader\board_types.txt contents to convert a APJ_BOARD_ID str to a int |
| 144 | + apj_board_id = int(board_types[apj_board_id_str]) |
| 145 | + apj_board_ids[apj_board_id] = dirname |
| 146 | + |
| 147 | + if product_string is None: |
| 148 | + print(f"{dirname}") |
| 149 | + product_string = dirname |
| 150 | + |
| 151 | + # If we successfully found a vendor, manufacturer, and products, add to the result dict |
| 152 | + if vendor_id is not None and vendor_string is not None: |
| 153 | + if vendor_id not in result: |
| 154 | + result[vendor_id] = {"vendor": vendor_string, "PID": {}} |
| 155 | + |
| 156 | + # Add the product ID and corresponding product string |
| 157 | + result[vendor_id]["PID"][product_id] = product_string |
| 158 | + |
| 159 | + |
| 160 | +def pretty_print_dict(d: dict, indent: int = 4, format_int_in_hex: bool = True) -> str: |
| 161 | + """Pretty prints a dictionary, formatting integers in hexadecimal to a string.""" |
| 162 | + output = io.StringIO() |
| 163 | + for key, value in d.items(): |
| 164 | + # Format the key |
| 165 | + formatted_key = ( |
| 166 | + f"0x{key:04X}" if isinstance(key, int) and format_int_in_hex else f'"{key}"' if isinstance(key, str) else key |
| 167 | + ) |
| 168 | + # Format the value |
| 169 | + if isinstance(value, dict): |
| 170 | + output.write(" " * indent + f"{formatted_key}: {{\n") |
| 171 | + output.write(pretty_print_dict(value, indent + 4)) # Recursively pretty print nested dicts |
| 172 | + output.write(" " * indent + "},\n") |
| 173 | + elif isinstance(value, int) and format_int_in_hex: |
| 174 | + formatted_value = f"0x{value:X}" |
| 175 | + output.write(" " * indent + f"{formatted_key}: {formatted_value},\n") |
| 176 | + elif isinstance(value, str): |
| 177 | + output.write(" " * indent + f'{formatted_key}: "{value}",\n') |
| 178 | + else: |
| 179 | + output.write(" " * indent + f"{formatted_key}: {value},\n") |
| 180 | + |
| 181 | + return output.getvalue() # Return the captured string |
| 182 | + |
| 183 | + |
| 184 | +def write_to_file() -> None: |
| 185 | + directory = "MethodicConfigurator" |
| 186 | + os.makedirs(directory, exist_ok=True) # Create the directory if it doesn't exist |
| 187 | + file_path = os.path.join(directory, "middleware_fc_ids.py") |
| 188 | + |
| 189 | + with open(file_path, "w", encoding="utf-8") as file: |
| 190 | + file.write("# File automatically generated by the update_supported_flight_controllers.py script\n") |
| 191 | + file.write("# Do not edit directly. ALL CHANGES WILL BE OVERWRITTEN\n") |
| 192 | + file.write("\n") |
| 193 | + file.write("from typing import Union\n") |
| 194 | + file.write("\n") |
| 195 | + file.write("SUPPORTED_VIDS_AND_PIDS: dict[int, dict[str, Union[str, dict[int, str]]] ] = {\n") |
| 196 | + file.write(pretty_print_dict(result)) # Use pretty_print_dict to format the output |
| 197 | + file.write("\n") |
| 198 | + file.write("}\n") |
| 199 | + file.write("\n") |
| 200 | + file.write("APJ_BOARD_IDS: dict[int, str] = {\n") |
| 201 | + file.write(pretty_print_dict(apj_board_ids, format_int_in_hex=False)) # Use pretty_print_dict to format the output |
| 202 | + file.write("}\n") |
| 203 | + file.write("\n") |
| 204 | + file.write("APJ_BOARD_IDS_BDSHOT: dict[int, str] = {\n") |
| 205 | + file.write( |
| 206 | + pretty_print_dict(apj_board_ids_bdshot, format_int_in_hex=False) |
| 207 | + ) # Use pretty_print_dict to format the output |
| 208 | + file.write("}\n") |
| 209 | + file.write("\n") |
| 210 | + |
| 211 | + |
| 212 | +def main() -> None: |
| 213 | + read_board_types_txt() |
| 214 | + search_hwdef_files(base_dir) |
| 215 | + write_to_file() |
| 216 | + |
| 217 | + |
| 218 | +if __name__ == "__main__": |
| 219 | + main() |
0 commit comments