Skip to content

Commit 38212b3

Browse files
committed
FEATURE: Auto detect correct board name and if possible maufacturer.
1 parent c2b8bda commit 38212b3

File tree

2 files changed

+221
-2
lines changed

2 files changed

+221
-2
lines changed

MethodicConfigurator/backend_flightcontroller_info.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"""
1010

1111
from collections.abc import Sequence
12-
from typing import Any, Union
12+
from typing import Union
1313

1414
from pymavlink import mavutil
1515

@@ -236,7 +236,7 @@ def __classify_vehicle_type(mav_type_int: int) -> str:
236236
return mav_type_to_vehicle_type.get(mav_type_int, "")
237237

238238
@staticmethod
239-
def __list_ardupilot_supported_usb_pid_vid() -> dict[int, dict[str, Any]]:
239+
def __list_ardupilot_supported_usb_pid_vid() -> dict[int, dict[str, Union[str, dict[int, str]]]]:
240240
"""
241241
List all ArduPilot supported USB vendor ID (VID) and product ID (PID).
242242

update_flight_controller_ids.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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

Comments
 (0)